package org.tinygroup.signature;

import java.io.ByteArrayOutputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collections;
import java.util.List;

import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.integration.transformer.MessageTransformationException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.util.Assert;
import org.tinygroup.commons.tools.StringUtil;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;
import org.w3c.dom.Element;

/**
 * xml加签验签处理的拦截器
 * 
 * @author renhui
 * 
 */
public abstract class AbstractSignChannelInterceptor extends
		ChannelInterceptorAdapter implements InitializingBean, BeanFactoryAware {

	private ObjectToDocumentTransformer objectToDocumentTransformer;
	protected XMLSignatureFactory xmlSignatureFactory;
	protected KeyPair keyPair;
	protected XmlSignatureConfig config;
	private volatile MessageBuilderFactory messageBuilderFactory;
	private BeanFactory beanFactory;

	protected static final Logger LOGGER = LoggerFactory
			.getLogger(AbstractSignChannelInterceptor.class);

	@Override
	public Message<?> preSend(Message<?> message, MessageChannel channel) {
		try {
			Element document = (Element) objectToDocumentTransformer
					.doTransform(message);
			String singurateXml = internalProcessor(document);
			if (singurateXml == null) {
				return null;
			}
			return messageBuilderFactory.withPayload(singurateXml)
					.copyHeaders(message.getHeaders()).build();

		} catch (MessageTransformationException e) {
			throw e;
		} catch (Exception e) {
			throw new MessageTransformationException(message,
					"failed to transform message", e);
		}
	}

	/**
	 * 加签\解签处理流程
	 * 
	 * @param document
	 * @return
	 * @throws Exception
	 */
	protected abstract String internalProcessor(Element element)
			throws Exception;

	/**
	 * 输出签名完毕的XML
	 * 
	 * @param doc
	 * @param output
	 * @throws Exception
	 */
	protected String transform(Element element) throws Exception {
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		transformer.transform(new DOMSource(element), new StreamResult(output));
		return new String(output.toByteArray());
	}

	protected List<Reference> createReference() throws Exception {
		Transform envelopedTransform = xmlSignatureFactory.newTransform(
				Transform.ENVELOPED, (TransformParameterSpec) null);
		DigestMethod sha1DigMethod = xmlSignatureFactory.newDigestMethod(
				DigestMethod.SHA1, null);
		Reference ref = xmlSignatureFactory.newReference("", sha1DigMethod,
				Collections.singletonList(envelopedTransform), null, null);
		return Collections.singletonList(ref);
	}

	// 根据PublicKey和PrivateKey创建密钥对
	private KeyPair loadKeyPair(XmlSignatureConfig config) throws Exception {
		try {
			LOGGER.logMessage(LogLevel.DEBUG, "开始加载PrivateKey信息...");
			PrivateKey privateKey = loadPrivateKey(config);
			LOGGER.logMessage(LogLevel.DEBUG, "加载PrivateKey信息成功");
			LOGGER.logMessage(LogLevel.DEBUG, "开始加载PublicKey信息...");
			PublicKey publicKey = loadPublicKey(config);
			LOGGER.logMessage(LogLevel.DEBUG, "加载PublicKey信息成功");
			return new KeyPair(publicKey, privateKey);
		} catch (Exception e) {
			throw new Exception(String.format("根据配置项[%s]生成KeyPair失败",
					config.toString()), e);
		}
	}

	/**
	 * 创建KeyInfo
	 * 
	 * @param config
	 * @return
	 * @throws Exception
	 */
	protected KeyInfo createKeyInfo(XmlSignatureConfig config) throws Exception {
		KeyInfoFactory keyInfoFac = xmlSignatureFactory.getKeyInfoFactory();
		KeyValue keyValue = keyInfoFac.newKeyValue(keyPair.getPublic());
		return keyInfoFac.newKeyInfo(Collections.singletonList(keyValue));
	}

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(config, "config must not null");
		this.messageBuilderFactory = IntegrationUtils
				.getMessageBuilderFactory(this.beanFactory);
		objectToDocumentTransformer = new ObjectToDocumentTransformer();
		String mechanismType = config.getMechanismType();
		xmlSignatureFactory = StringUtil.isBlank(mechanismType) ? XMLSignatureFactory
				.getInstance() : XMLSignatureFactory.getInstance(mechanismType);
		this.keyPair = loadKeyPair(config);
	}

	// 装载私钥
	private PrivateKey loadPrivateKey(XmlSignatureConfig config)
			throws Exception {
		String storeType = StringUtil.isEmpty(config.getPrivateStoreType()) ? KeyStore
				.getDefaultType() : config.getPrivateStoreType();
		KeyStore keyStore = KeyStore.getInstance(storeType);
		char[] password = config.getPassword().toCharArray();
		keyStore.load(config.getPrivateKey().getInputStream(), password);
		return (PrivateKey) keyStore.getKey(config.getAlias(), password);
	}

	// 装载公钥
	private PublicKey loadPublicKey(XmlSignatureConfig config) throws Exception {
		String storeType = StringUtil.isEmpty(config.getPublicStoreType()) ? "X.509"
				: config.getPublicStoreType();
		CertificateFactory cf = CertificateFactory.getInstance(storeType);
		Certificate c = cf.generateCertificate(config.getPublicKey()
				.getInputStream());
		return c.getPublicKey();
	}

	public void setConfig(XmlSignatureConfig config) {
		this.config = config;
	}

	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

}
